/*
 * Copyright 2009 Jamie Gennis
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.code.gwt.remoteaction.rebind;

import com.google.code.gwt.remoteaction.client.AbstractActionProxy;
import com.google.code.gwt.remoteaction.client.ActionError;
import com.google.code.gwt.remoteaction.client.ActionResult;
import com.google.code.gwt.remoteaction.client.ActionRuntimeException;
import com.google.code.gwt.remoteaction.server.Utils;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.impl.ArtificialRescue;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.rpc.linker.RpcDataArtifact;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Creates an ActionResult implementation class.
 *
 * @author jgennis
 */
public class ActionResultCreator {

  protected JClassType actionType;

  protected JClassType resultType;

  protected Set<JClassType> throwableTypes = new HashSet<JClassType>();

  public ActionResultCreator(JClassType actionType, JClassType resultType,
      Collection<JClassType> throwableTypes) {
    this.actionType = actionType;
    this.resultType = resultType;
    this.throwableTypes.addAll(throwableTypes);
  }

  public String create(TreeLogger logger, GeneratorContext context)
      throws UnableToCompleteException {
    TypeOracle typeOracle = context.getTypeOracle();
    JClassType actionRuntimeExType = typeOracle.findType(ActionRuntimeException.class.
        getCanonicalName());
    JClassType actionErrorType = typeOracle.findType(ActionError.class.getCanonicalName());
    throwableTypes.add(actionRuntimeExType);
    throwableTypes.add(actionErrorType);

    SourceWriter srcWriter = getSourceWriter(logger, context);
    if (srcWriter == null)
      return getResultCanonicalName();

    generateResultFields(logger, srcWriter);
    generateResultMethods(logger, srcWriter);

    srcWriter.commit(logger);

    registerRpcDataArtifact(logger, context);

    return getResultCanonicalName();
  }

  public String getRescue() {
    StringBuilder sb = new StringBuilder();
    sb.append("@Rescue(\n").
        append("    className = \"").append(getResultCanonicalName()).append("\",\n").
        append("    instantiable = true,\n").
        append("    fields = {\n").
        append("      \"result\",\n").
        append("      \"thrown\"").
        append("})");
    return sb.toString();
  }

  protected void generateResultFields(TreeLogger logger, SourceWriter srcWriter)
      throws UnableToCompleteException {
    srcWriter.println();
    srcWriter.println("public " + resultType.getParameterizedQualifiedSourceName() + " result;");
    srcWriter.println();
    srcWriter.println("public Throwable thrown;");
  }

  protected void generateResultMethods(TreeLogger logger, SourceWriter srcWriter)
      throws UnableToCompleteException {
    /*
     * @Override
     * public T getResult() {
     *   return result;
     * }
     */
    srcWriter.println();
    srcWriter.println("@Override");
    srcWriter.println("public " + resultType.getParameterizedQualifiedSourceName() +
        " getResult() {");
    srcWriter.indentln("return result;");
    srcWriter.println("}");

    /*
     * @Override
     * public Throwable getException() {
     *   return thrown;
     * }
     */
    srcWriter.println();
    srcWriter.println("@Override");
    srcWriter.println("public Throwable getException() {");
    srcWriter.indentln("return thrown;");
    srcWriter.println("}");
  }

  protected void registerRpcDataArtifact(TreeLogger logger, GeneratorContext context)
      throws UnableToCompleteException {
    RpcDataArtifact artifact = new RpcDataArtifact(getResultCanonicalName());
    List<String> names = new ArrayList<String>();
    names.add("result");
    names.add("thrown");
    artifact.setFields(getResultCanonicalName(), names);
    context.commitArtifact(logger, artifact);
  }

  protected SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext context)
      throws UnableToCompleteException {
    PrintWriter printWriter = context.tryCreate(logger, Utils.INTERNAL_PACKAGE,
        getResultSimpleName());
    if (printWriter == null) {
      return null;
    }

    TypeOracle typeOracle = context.getTypeOracle();
    JClassType actionResultType = typeOracle.findType(ActionResult.class.getCanonicalName());

    ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
        Utils.INTERNAL_PACKAGE, getResultSimpleName());

    String[] imports = new String[]{
      GWT.class.getCanonicalName(),
      AsyncCallback.class.getCanonicalName(),
      ArtificialRescue.class.getCanonicalName(),
      ArtificialRescue.Rescue.class.getCanonicalName(),
      AbstractActionProxy.class.getCanonicalName(),
      ActionResult.class.getCanonicalName()
    };
    for (String imp : imports)
      composerFactory.addImport(imp);

    composerFactory.addImplementedInterface(actionResultType.getName());

    return composerFactory.createSourceWriter(context, printWriter);
  }

  protected String getResultCanonicalName() {
    return Utils.getResultCanonicalClassName(actionType.getQualifiedSourceName());
  }

  protected String getResultSimpleName() {
    return Utils.getResultSimpleClassName(actionType.getQualifiedSourceName());
  }
}
